/**
 * @file simple_message_client.c
 * Verteilte Systeme - TCP/IP Programmierübung
 *
 * tcpip client
 *
 * @author Theresa Meiksner - ic12b028 - theresa.meiksner@technikum-wien.at
 * @author Ronald Wimmer    - ic12b070 - ronald.wimmer@technikum-wien.at
 * @date 2013/12/09
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <netdb.h>
#include <unistd.h>
#include "simple_message_client_commandline_handling.h"

/* maximum client input stream size */
#define MAX_CLIENT_INPUTSTREAM_SIZE 8192
/* maximum client output stream size */
#define MAX_CLIENT_OUTPUTSTREAM_SIZE 256

/* maximum size for the fileName attribute in the response */
#define MAX_FILENAME_SIZE 256
/* maximum size for the length attribute in the response */
#define MAX_LENGTH_SIZE 10

/**
 * \brief method for finding the attribute value of the given attribute identifier
 * in the given byte array
 *
 * \param data - the byte array, where we want to search in
 * \param identifier - the attribute identifier to be searched
 * \param value - the found attribute value

 * \return char* pointer
 * \retval the pointer, after the found value, so we can search the next item
 *
 */
char* getValueFromByteArray(char* data, const char *identifier, char *value);

/**
 * \brief error handling routine
 *
 * \param location - info about the location, where the error happened
 * \param errorMessage - additional information about the specific error
 *
 */
void showErrorMessageAndExit(char* location, const char* errorMessage);

/**
 * \brief method for writing the files from the server response byte array
 *
 * \param inputData - the byte array from the server
 */
void writeFiles(char* inputData);

/**
 * \brief function for: smc_parsecommandline, see the doc of this function for further
 * information
 *
 * \param stream - the stream to write
 * \param message - the message to write
 * \param exitcode - the exitcode
 */
void showUsage(FILE * stream, const char * message, int exitcode);

 /**
  * \brief this function reads the response from the server in the byte array
  *
  * \param inputData - the byte array to be filled from the response
 */
void readData(char *inputData);

/**
 * \brief this function sends the request to the server
 *
 */
void submitMessage();

/**
 * \brief only one simple send would not suffice, we must send in a loop
 *
 * \param sock_fd - the socket handle
 * \param buf - buffer to write
 * \param len - the length of the message
 */
int sendAll(int sock_fd, char *buf, int *len);

/* socket handle */
int socketFileDescriptor = 0;

/* file handle for output files written from server response stream */
FILE *outputFileDescriptor = NULL;

/* program name for error handling */
const char *programName = NULL;

/* client argument values*/
const char *server, *port, *user, *message, *image;
int verbose = 0;
char verboseMessage[256];

/* macro for verbose */
#define verbose(count) \
if (verbose != 0) \
{ \
	if (count >= 0) \
	{ \
	  	printf("%s [%s, %s, line %d]: %s\n", programName, __FILE__, __FUNCTION__,  __LINE__, verboseMessage); \
		memset(verboseMessage, 0, sizeof(verboseMessage)); \
	} \
}

int main(int argc, const char * const argv[]) {

	/* byte array for request sent to server */
	char inputData[MAX_CLIENT_INPUTSTREAM_SIZE];

	programName = argv[0];

	/* get arguments by using the library provided */
    smc_parsecommandline(argc, argv, &showUsage, &server, &port, &user, &message, &image, &verbose);

    verbose(sprintf(verboseMessage, "using arguments server: %s, port: %s, user: %s, message: %s, image: %s", server, port, user, message, image));

    /* submit message to server */
    submitMessage();

    /* receive request */
    readData(inputData);

	/* write files from response */
	writeFiles(inputData);

	verbose(sprintf(verboseMessage, "client processing quit normally"));

	return 0;
}


void readData(char *inputData) {
	int bytesRead = 0;

    verbose(sprintf(verboseMessage, "calling shutdown."));

    /* calling shutdown write, client can still receive data */
	if (shutdown(socketFileDescriptor, SHUT_WR) != 0) {
		showErrorMessageAndExit("shutdown()", "error while calling shutdown()");
	}

	verbose(sprintf(verboseMessage, "receiving data from socket."));

    /* read response stream from server into inputData array */
	bytesRead = recv(socketFileDescriptor, inputData, MAX_CLIENT_INPUTSTREAM_SIZE, MSG_WAITALL);
	verbose(sprintf(verboseMessage, "%d bytes read from socket.", bytesRead));

	if (bytesRead <=0)	{
		showErrorMessageAndExit("recv()", "error while receiving data from socket [recv()]");
	}

	verbose(sprintf(verboseMessage, "closing socket with handle: %d", socketFileDescriptor));

	if (close(socketFileDescriptor) != 0) {
		  showErrorMessageAndExit("close()", "error while closing socket [close()]");
	}
}

void submitMessage() {
	/* byte array for server response */
	char outputData[MAX_CLIENT_OUTPUTSTREAM_SIZE];
	/* structures for socket handling */
	struct addrinfo hints, *socket_address;
	int status = 0, count, length;

	/* initialize the structure */
	memset(&hints, 0, sizeof hints);

	/* UNSPEC = ipv4 and ipv6*/
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

    verbose(sprintf(verboseMessage, "get address info"));

	/* get address info struct */
	if ((status = getaddrinfo(server, port, &hints, &socket_address)) != 0) {
		showErrorMessageAndExit("getaddrinfo", gai_strerror(status));
	}

    verbose(sprintf(verboseMessage, "calling socket()"));

	if ((socketFileDescriptor = socket(socket_address->ai_family,
		 socket_address->ai_socktype, socket_address->ai_protocol)) == -1) {
		showErrorMessageAndExit("socket()", "error while calling socket()");
	}

    verbose(sprintf(verboseMessage, "got socket handle: %d", socketFileDescriptor));

    verbose(sprintf(verboseMessage, "calling connect on: %s:%s", server, port));

    if (connect(socketFileDescriptor, socket_address->ai_addr, socket_address->ai_addrlen) == -1) {
		showErrorMessageAndExit("connect()", "error while calling connect()");
	}

    verbose(sprintf(verboseMessage, "freeing the socket address."));
	/* free the structure */
	freeaddrinfo(socket_address);

    verbose(sprintf(verboseMessage, "building request message."));

	/* the string to be sent, depending on if optional i argument is given */
	if (image == NULL) {
		count = sprintf(outputData, "user=%s\n%s\n", user, message);
	} else {
		count = sprintf(outputData, "user=%s\nimg=%s\n%s\n", user, image, message);
	}

    verbose(sprintf(verboseMessage, "sending request message: %s", outputData));

	if (count < 0) {
		showErrorMessageAndExit("sprintf()", "error while calling sprintf()");
	}

	length = strlen(outputData);

	if (sendAll(socketFileDescriptor, outputData, &length) == -1) {
		showErrorMessageAndExit("sendAll()", "error while sending text to server by calling sendAll()");
	}
}

void writeFiles(char* inputData) {
	/* Keys for reading the attributes from server response stream */
	const char *FILE_IDENTIFIER = "file=";
	const char *STATE_IDENTIFIER = "status=";
	const char *LENGTH_IDENTIFIER = "len=";

	/* variables for attribute values */
	char length[MAX_LENGTH_SIZE];
	long lengthNumber = 0;
	char fileName[MAX_FILENAME_SIZE];
	char businessLogicState[1];

	/* pointer for processing response byte array */
	char *pch = NULL;

	/* get BusinessLogic state out of response stream */
	pch = getValueFromByteArray(inputData, STATE_IDENTIFIER, businessLogicState);

	/* BusinessLogic state bit is set to 0 (OK) */
	if (businessLogicState[0] == '0') {
	    verbose(sprintf(verboseMessage, "server call = OK..."));

	    /* read until no more 'file'-keys are found in stream */
		while (pch != NULL) {
			/* get file name*/
			pch = getValueFromByteArray(pch, FILE_IDENTIFIER, fileName);
			if (pch != NULL) {
			    /* get file length */
			    pch = getValueFromByteArray(pch, LENGTH_IDENTIFIER, length);
			    if (pch == NULL) {
				showErrorMessageAndExit("getValueFromByteArray()", "error retrieving length value from response stream.");
			    }

		    	verbose(sprintf(verboseMessage, "opening file in write mode: %s", fileName));
			    /* open file with given fileName in write mode */
			   	if ((outputFileDescriptor = fopen(fileName, "w")) == NULL ) {
					showErrorMessageAndExit("fopen()", fileName);
			   	}

			   	/* convert into numeric type */
			   	if ((lengthNumber = strtol(length, NULL, 0)) == 0)
			   	{
					showErrorMessageAndExit("strtol()", length);
			   	}

			   	verbose(sprintf(verboseMessage, "writing bytes to file: %s", fileName));

			   	/* write byte array to file */
			   	if (fwrite (pch , 1 , lengthNumber , outputFileDescriptor ) == 0) {
					showErrorMessageAndExit("fwrite()", fileName);
			   	}

			   	verbose(sprintf(verboseMessage, "%ld bytes written to file: %s", lengthNumber, fileName));
			   	verbose(sprintf(verboseMessage, "closing file: %s", fileName));

			   	/* close file */
			   	if (fclose (outputFileDescriptor ) == EOF) {
					showErrorMessageAndExit("fwrite()", fileName);
			   	}
			   	verbose(sprintf(verboseMessage, "file: %s closed successfully", fileName));
			}
 		}
	} else {
       	verbose(sprintf(verboseMessage, "business logic call = NOT OK, response = %s", inputData));
		showErrorMessageAndExit("response error: ", inputData);
	}

}

char * getValueFromByteArray(char* data, const char *identifier, char *value) {
	char *nch;
	char *ptr;

	verbose(sprintf(verboseMessage, "parsing attribute with identifier '%s' from response byte array", identifier));

	if (data == NULL) {
	   verbose(sprintf(verboseMessage, "input stream is null, identifier is: %s", identifier));
   	   return NULL;
	}

	/* initialize the value */
	memset(value, 0, sizeof(value));

	/* search for the given identifier */
	ptr = strstr(data, identifier);

	/* not found */
	if (ptr == NULL) {
	   verbose(sprintf(verboseMessage, "identifier not found: %s", identifier));
   	   return NULL;
	}

	/* set the pointer after the '=' character */
	ptr = ptr + strlen(identifier);

	/* determine position of newline */
	nch = strchr(ptr, '\n');

	/* no new line found => error in format */
	if (nch == NULL) {
	   verbose(sprintf(verboseMessage, "newline not found for attribute value with identifier: %s", identifier));
   	   return NULL;
	}

	/* extract value from value and copy it into our value variable */
	strncpy(value, ptr, nch - ptr);

	/* position the pointer after the value, in order to search the next item */
	ptr = ptr + strlen(value) + 1;

	verbose(sprintf(verboseMessage, "attribute value of identifier '%s' is: %s", identifier, value));

	return ptr;
}

/* beej code */
int sendAll(int s, char *buf, int *len) {
	int total = 0;
	int bytesleft = *len;
	int n;

	while (total < *len) {
		n = send(s, buf + total, bytesleft, 0);
		if (n == -1) {
			break;
		}
		total += n;
		bytesleft -= n;
	}
	*len = total;
	return n == -1 ? -1 : 0;
}

void showErrorMessageAndExit(char* location, const char* errorMessage) {
	fprintf(stderr, "last errno: \n");
	perror(programName);
	fprintf(stderr, "Error occured in - %s: %s\n", location, errorMessage);
	exit(EXIT_FAILURE);
}

void showUsage(FILE * stream, const char * message, int exitcode) {
 	fprintf(stream, message);
	exit(exitcode);
}

